/*
基本概念

Generator 函数是 ES6 提供的一种异步编程解决方案，语法行为与传统函数完全不同。
本章详细介绍 Generator 函数的语法和 API，它的异步编程应用请看《Generator 函数的异步应用》一章。

Generator 函数有多种理解角度。语法上，首先可以把它理解成，Generator 函数是一个状态机，封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象，也就是说，Generator 函数除了状态机，还是一个遍历器对象生成函数。
返回的遍历器对象，可以依次遍历 Generator 函数内部的每一个状态。

形式上，Generator 函数是一个普通函数，但是有两个特征。
一是，function关键字与函数名之间有一个星号；简写时*在左边
二是，函数体内部使用yield表达式，定义不同的内部状态（yield在英语里的意思就是“产出”）。
*/



/*
义了一个 Generator 函数helloWorldGenerator，它内部有两个yield表达式（hello和world），
即该函数有三个状态：hello，world 和 return 语句（结束执行）。
然后，Generator 函数的调用方法与普通函数一样，也是在函数名后面加上一对圆括号。
不同的是，调用 Generator 函数后，该函数并不执行，返回的也不是函数运行结果，而是一个指向内部状态的指针对象，
也就是上一章介绍的遍历器对象（Iterator Object）。

下一步，必须调用遍历器对象的next方法，使得指针移向下一个状态。
也就是说，每次调用next方法，内部指针就从函数头部或上一次停下来的地方开始执行，直到遇到下一个yield表达式（或return语句）为止。
换言之，Generator 函数是分段执行的，yield表达式是暂停执行的标记，而next方法可以恢复执行。
*/

function* helloWorldGenerator() {
	yield 'hello';
	yield 'world';
	return 'ending';
}

var hw = helloWorldGenerator(); //需要遍历才能看到结果  循环就是默认执行next方法

/*
总结一下，调用 Generator 函数，返回一个遍历器对象，代表 Generator 函数的内部指针。
以后，每次调用遍历器对象的next方法，就会返回一个有着value和done两个属性的对象。
value属性表示当前的内部状态的值，是yield表达式后面那个表达式的值；done属性是一个布尔值，表示是否遍历结束。
*/


/*
ES6 没有规定，function关键字与函数名之间的星号，写在哪个位置。这导致下面的写法都能通过。
由于 Generator 函数仍然是普通函数，所以一般的写法是上面的第三种，即星号紧跟在function关键字后面。本书也采用这种写法。
*/
function* foo(x, y) {···}

function* foo(x, y) {···}

function* foo(x, y) {···}

function* foo(x, y) {···}




/*
由于 Generator 函数返回的遍历器对象，只有调用next方法才会遍历下一个内部状态，所以其实提供了一种可以暂停执行的函数。
yield表达式就是暂停标志。

遍历器对象的next方法的运行逻辑如下。

（1）遇到yield表达式，就暂停执行后面的操作，并将紧跟在yield后面的那个表达式的值，作为返回的对象的value属性值。

（2）下一次调用next方法时，再继续往下执行，直到遇到下一个yield表达式。

（3）如果没有再遇到新的yield表达式，就一直运行到函数结束，直到return语句为止，并将return语句后面的表达式的值，作为返回的对象的value属性值。

（4）如果该函数没有return语句，则返回的对象的value属性值为undefined。

需要注意的是，yield表达式后面的表达式，只有当调用next方法、内部指针指向该语句时才会执行，
因此等于为 JavaScript 提供了手动的“惰性求值”（Lazy Evaluation）的语法功能。
*/

function* gen() {
	yield 123 + 456;
}
//上面代码中， yield后面的表达式123 + 456， 不会立即求值， 只会在next方法将指针移到这一句时， 才会求值。



/*
yield表达式与return语句既有相似之处，也有区别。
相似之处在于，都能返回紧跟在语句后面的那个表达式的值。
区别在于每次遇到yield，函数暂停执行，下一次再从该位置继续向后执行，
而return语句不具备位置记忆的功能。一个函数里面，只能执行一次（或者说一个）return语句，但是可以执行多次（或者说多个）yield表达式。
正常函数只能返回一个值，因为只能执行一次return；
Generator 函数可以返回一系列的值，因为可以有任意多个yield。
从另一个角度看，也可以说 Generator 生成了一系列的值，这也就是它的名称的来历（英语中，generator 这个词是“生成器”的意思）。
*/


/*
Generator 函数可以不用yield表达式，这时就变成了一个单纯的暂缓执行函数。
函数f如果是普通函数，在为变量generator赋值时就会执行。
但是，函数f是一个 Generator 函数，就变成只有调用next方法时，函数f才会执行。
*/

function* f() {
	console.log('执行了！')
}

var generator = f();

setTimeout(function() {
	generator.next()
}, 2000);



/*
另外需要注意， yield表达式只能用在 Generator 函数里面， 用在其他地方都会报错。

*/
(function() {
	yield 1;
})()
// SyntaxError: Unexpected number


/*
下面是另外一个例子。
上面代码也会产生句法错误，因为forEach方法的参数是一个普通函数，
但是在里面使用了yield表达式（这个函数里面还使用了yield*表达式，详细介绍见后文）。
一种修改方法是改用for循环。
*/
var arr = [1, [
		[2, 3], 4
	],
	[5, 6]
];
var flat = function*(a) {
	a.forEach(function(item) {
		if (typeof item !== 'number') {
			yield* flat(item);
		} else {
			yield item;
		}
	});
};
for (var f of flat(arr)) {
	console.log(f);
}



//另外，yield表达式如果用在另一个表达式之中，必须放在圆括号里面。

function* demo() {
	console.log('Hello' + yield); // SyntaxError
	console.log('Hello' + yield 123); // SyntaxError

	console.log('Hello' + (yield)); // OK
	console.log('Hello' + (yield 123)); // OK  这样写需要两次next 控制台才会打印
}



/*
yield表达式用作函数参数或放在赋值表达式的右边，可以不加括号。
*/
function* demo() {
	foo(yield 'a', yield 'b'); // 需要两次next才会执行foo函数
	let input = yield; // yield后没有产物不会计入循环
}




/*
上一章说过，任意一个对象的Symbol.iterator方法，
等于该对象的遍历器生成函数，调用该函数会返回该对象的一个遍历器对象。

由于 Generator 函数就是遍历器生成函数，
因此可以把 Generator 赋值给对象的Symbol.iterator属性，从而使得该对象具有 Iterator 接口。

最简单的添加[Symbol.iterator]接口的方法就是用generator函数
它会自动部署遍历接口代码
*/


/*
Generator 函数赋值给Symbol.iterator属性，从而使得myIterable对象具有了 Iterator 接口，可以被...运算符遍历了。
*/
var myIterable = {};
myIterable[Symbol.iterator] = function*() {
	yield 1;
	yield 2;
	yield 3;
};

[...myIterable] // [1, 2, 3]



/*
Generator 函数执行后，返回一个遍历器对象。
该对象本身也具有Symbol.iterator属性，执行后返回自身。
*/
function* gen() {
	// some code
}
var g = gen();
g[Symbol.iterator]() === g
